package org.ihelpuoo.lang;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;

import org.ihelpuoo.I;
import org.ihelpuoo.io.CharInputStream;
import org.ihelpuoo.io.FileUtils;
import org.ihelpuoo.io.StreamUtils;
import org.ihelpuoo.localize.DateTimeUtils;

public class ObjectUtils
{

	private static final String	DF_DATETIME	= "yyyy-MM-dd HH:mm:ss";
	private static final String	DF_DATE		= "yyyy-MM-dd";

	public static String getSimpleName(String name)
	{
		if (null == name)
			return null;
		int pos = name.lastIndexOf('.');
		return name.substring(pos + 1);
	}

	public static String dumpMe(Object obj)
	{
		return dump(obj,0,false);
	}

	public static String dumpMe(Object obj, int indent)
	{
		return dump(obj,indent,false);
	}

	public static String dumpAll(Object obj)
	{
		return dump(obj,0,true);
	}

	public static String dumpAll(Object obj, int indent)
	{
		return dump(obj,indent,true);
	}

	public static String dump(Object obj, int indent, boolean isRecursive)
	{
		if (null == obj)
			return "NULL Object!!!";
		String prefix = StringUtils.dup("\t",indent).toString();
		String re = "\n" + prefix + obj.hashCode() + "[" + obj.getClass().getName() + "]";

		re += "\n" + prefix + "{";
		prefix += "\t";
		List<Field> fList = new LinkedList<Field>();
		Class klass = obj.getClass();
		while (klass.getSuperclass() != null)
		{
			Field[] fs = klass.getDeclaredFields();
			for (int i = 0; i < fs.length; i++)
			{
				fList.add(fs[i]);
			}
			klass = klass.getSuperclass();
		}
		Collections.sort(fList,new FieldNameComparator());
		Iterator it = fList.iterator();

		while (it.hasNext())
		{
			String nvs = "";
			Field f = (Field)it.next();
			nvs += "\n" + prefix;
			nvs += f.getName();
			if (Modifier.isPublic(f.getModifiers()))
			{
				try
				{
					Object value = f.get(obj);
					nvs += " = " + dumpValue(value,indent + 1,isRecursive);
				}
				catch (Exception e)
				{
					nvs += " <UNKNOWN>";
				}

			}
			else
			{
				String getterName = "get" + StringUtils.capitalize(f.getName());
				try
				{
					Method getter = obj.getClass().getMethod(getterName,(Class[])null);
					Object value = getter.invoke(obj,(Object[])null);
					nvs += " = " + dumpValue(value,indent + 1,isRecursive);
				}
				catch (Exception e)
				{
					nvs += " <UNKNOWN>";
				}

			}
			re += nvs;
		}
		re += "\n" + prefix.substring(1) + "}";
		return re;
	}

	private static String dumpValue(Object value, int indent, boolean isRecursive)
	{
		String re = "";
		try
		{
			if (value instanceof Boolean)
			{
				re += value.toString();
			}
			else if (value instanceof Character)
			{
				re += '\'' + value.toString() + '\'';
			}
			else if (value instanceof Byte)
			{
				re += value.toString();
			}
			else if (value instanceof Short)
			{
				re += value.toString();
			}
			else if (value instanceof Integer)
			{
				re += value.toString();
			}
			else if (value instanceof Long)
			{
				re += value.toString();
			}
			else if (value instanceof Float)
			{
				re += value.toString();
			}
			else if (value instanceof Double)
			{
				re += value.toString();
			}
			else if (value instanceof Void)
			{
				re += value.toString();
			}
			else if ((value instanceof String) || (value instanceof StringBuffer))
			{
				re += '"' + value.toString() + '"';
			}
			else if (value instanceof Collection)
			{
				re += "{...}/" + ((Collection)value).size();
			}
			else if (value.getClass().isArray())
			{

				re += "[...]/" + Array.getLength(value);

			}
			else if (isRecursive)
			{
				re += dump(value,indent + 2,true);
			}
			else
			{
				re += value.toString();
			}
		}
		catch (Exception e)
		{
			re += "!!!";
		}
		return re;
	}

	public static String dumpGetter(Object obj)
	{
		Method[] methods = obj.getClass().getMethods();
		StringBuffer sb = new StringBuffer();
		sb.append(I.NEW_LINE + obj.getClass().getName());
		for (int i = 0; i < methods.length; i++)
		{
			if (isGetter(methods[i]))
			{
				sb.append(I.NEW_LINE + "\t"
						+ (Modifier.isStatic(methods[i].getModifiers()) ? "*" : " ")
						+ methods[i].getName() + "() -> ");
				try
				{
					Object value = methods[i].invoke(obj,(Object[])null);
					sb.append(dumpValue(value,0,false));
				}
				catch (Exception e)
				{
					throw new RuntimeException(e);
				}
			}
		}
		sb.append(I.NEW_LINE);
		return sb.toString();
	}

	public static boolean isGetter(Method method)
	{
		if (null == method)
			return false;
		if (method.getParameterTypes().length > 0)
			return false;
		if (method.getName().startsWith("get"))
			return true;
		if (method.getReturnType() == Boolean.class && method.getName().startsWith("is"))
			return true;
		return false;
	}

	public static boolean isPrimitive(Class type)
	{
		if (type.isPrimitive())
			return true;
		else if (type == Boolean.class)
			return true;
		else if (type == Character.class)
			return true;
		else if (type == Byte.class)
			return true;
		else if (type == Short.class)
			return true;
		else if (type == Integer.class)
			return true;
		else if (type == Long.class)
			return true;
		else if (type == Float.class)
			return true;
		else if (type == Double.class)
			return true;
		return false;
	}

	public static boolean isSetter(Method method)
	{
		if (null == method)
			return false;
		if (method.getParameterTypes().length != 1)
			return false;
		if (method.getName().startsWith("set"))
			return true;
		return false;
	}

	public static Method getGetter(Class type, Field field) throws NoSuchMethodException
	{
		Method m = null;
		String fn = field.getName();
		if ("boolean".equals(field.getType().getName()))
		{
			m = type.getMethod("is"
					+ StringUtils.capitalize((fn.startsWith("is") ? fn.substring(2) : fn)),
					(Class[])null);
		}
		else
		{
			m = type.getMethod("get" + StringUtils.capitalize(fn),(Class[])null);
		}
		if (m.getReturnType() != field.getType())
			return null;
		return m;
	}

	public static Method getGetter(Class type, String name) throws NoSuchMethodException
	{
		Method m = null;
		try
		{
			m = type.getMethod("get" + StringUtils.capitalize(name));
			if (isBoolean(m.getReturnType()))
				m = null;
		}
		catch (NoSuchMethodException e)
		{
		}
		if (null == m)
			try
			{
				m = type.getMethod("is" + StringUtils.capitalize(name));
				if (!isBoolean(m.getReturnType()))
					m = null;
			}
			catch (NoSuchMethodException e1)
			{
			}
		if (null == m)
			throw new NoSuchMethodException("can not find getter for '" + name + "' in class '"
					+ type.getName() + "'");
		return m;
	}

	public static Method getSetter(Class type, Field field) throws SecurityException,
			NoSuchMethodException
	{
		return getSetter(type,field.getName(),field.getType());
	}

	public static Method getSetter(Class type, String fn, Class argType)
			throws NoSuchMethodException
	{
		if ("boolean".equals(argType.getName()))
		{
			return type.getMethod("set"
					+ StringUtils.capitalize((fn.startsWith("is") ? fn.substring(2) : fn)),argType);
		}
		return type.getMethod("set" + StringUtils.capitalize(fn),argType);
	}

	/**
	 * @param klass: the host Class
	 * @param type: the Class type, can be supper class or interface
	 * @return klass is one of the instance of the type or not.
	 */
	public static boolean isChildOf(Class klass, Class type)
	{
		if (null == klass || null == type)
			return false;
		if (klass == type)
			return true;

		if (type.isInterface())
		{
			Class sup = klass;
			do
			{
				Class[] ints = sup.getInterfaces();
				for (int i = 0; i < ints.length; i++)
				{
					if (type == ints[i])
						return true;
				}
			}
			while ((sup = sup.getSuperclass()) != null);
		}
		else
		{
			Class sup = klass;
			while ((sup = sup.getSuperclass()) != null)
			{
				if (sup == type)
					return true;
			}
		}
		return false;
	}

	public static Field[] getAllMyStdFields(Class klass)
	{
		Class theClass = klass;
		LinkedList<Field> list = new LinkedList<Field>();
		while (null != theClass && !(theClass == Object.class))
		{
			Field[] fs = theClass.getDeclaredFields();
			for (int i = 0; i < fs.length; i++)
			{
				if (isIgnoredField(fs[i]))
					continue;
				if (fs[i].getName().charAt(0) == '_')
					continue;
				list.add(fs[i]);
			}
			theClass = theClass.getSuperclass();
		}
		Field[] fields = new Field[list.size()];
		Iterator it = list.iterator();
		int i = 0;
		while (it.hasNext())
		{
			fields[i++] = (Field)it.next();
		}
		return fields;
	}

	public static Field getField(Class klass, String name)
	{
		Class theClass = klass;
		while (null != theClass && !(theClass == Object.class))
		{
			Field f;
			try
			{
				f = theClass.getDeclaredField(name);
				return f;
			}
			catch (Exception e)
			{
				theClass = theClass.getSuperclass();
			}
		}
		return null;
	}

	public static boolean hasField(Class klass, String name)
	{
		return null != getField(klass,name);
	}

	public static Object getFieldValue(Object obj, Field f)
	{
		if (null == obj || null == f)
			return null;
		Object v = getFieldValueByGetter(obj,f);
		if (null != v)
			return v;
		if (f.isAccessible())
			try
			{
				return f.get(obj);
			}
			catch (Exception e)
			{
			}
		return null;
	}

	public static Object getFieldValue(Object obj, String fn)
	{
		// if there are the field
		Object re = getFieldValue(obj,getField(obj.getClass(),fn));
		// try getter
		if (null == re)
		{
			try
			{
				Method getter = ObjectUtils.getGetter(obj.getClass(),fn);
				re = getter.invoke(obj);
			}
			catch (Exception e)
			{
			}
		}
		return re;
	}

	public static Object getFieldValueByGetter(Object obj, Field f)
	{
		try
		{
			return ObjectUtils.getGetter(obj.getClass(),f).invoke(obj,(Object[])null);
		}
		catch (Exception e)
		{
			return null;
		}
	}

	public static Field[] getAllMyFields(Class klass)
	{
		Class theClass = klass;
		LinkedList<Field> list = new LinkedList<Field>();
		while (null != theClass && !(theClass == Object.class))
		{
			Field[] fs = theClass.getDeclaredFields();
			for (int i = 0; i < fs.length; i++)
			{
				if (isIgnoredField(fs[i]))
					continue;
				list.add(fs[i]);
			}
			theClass = theClass.getSuperclass();
		}
		Field[] fields = new Field[list.size()];
		Iterator it = list.iterator();
		int i = 0;
		while (it.hasNext())
		{
			fields[i++] = (Field)it.next();
		}
		return fields;
	}

	private static boolean isIgnoredField(Field f)
	{
		if (Modifier.isStatic(f.getModifiers()))
			return true;
		if (Modifier.isFinal(f.getModifiers()))
			return true;
		if (f.getName().startsWith("this$"))
			return true;
		return false;
	}

	public static Method[] getAllMyMethods(Class klass)
	{
		int size = 0;
		Class theClass = klass;
		while (null != theClass)
		{
			size += theClass.getDeclaredMethods().length;
			theClass = theClass.getSuperclass();
		}
		Method[] methods = new Method[size];
		theClass = klass;
		int index = 0;
		while (null != theClass)
		{
			Method[] ms = theClass.getDeclaredMethods();
			for (int i = 0; i < ms.length; i++)
			{
				index += i;
				methods[index] = ms[i];
			}
			theClass = theClass.getSuperclass();
		}
		return methods;
	}

	public static Object cast(Class type, String v) throws CastValueException
	{
		if (null == v)
			return null;
		try
		{
			if (type == Boolean.class || "boolean".equals(type.getName()))
			{
				return Boolean.valueOf(BooleanUtils.parseBoolean(v));
			}
			else if (type == Character.class || "char".equals(type.getName()))
			{
				if (v.length() == 0)
					return Character.valueOf((char)0);
				return Character.valueOf(v.charAt(0));
			}
			else if (type == Byte.class || "byte".equals(type.getName()))
			{
				if (StringUtils.isBlank(v))
					return -1;
				return Byte.valueOf(StringUtils.trim(v));
			}
			else if (type == Short.class || "short".equals(type.getName()))
			{
				if (StringUtils.isBlank(v))
					return -1;
				return Short.valueOf(StringUtils.trim(v));
			}
			else if (type == Integer.class || "int".equals(type.getName()))
			{
				if (StringUtils.isBlank(v))
					return -1;
				return Integer.valueOf(StringUtils.trim(v));
			}
			else if (type == Long.class || "long".equals(type.getName()))
			{
				if (StringUtils.isBlank(v))
					return -1;
				return Long.valueOf(StringUtils.trim(v));
			}
			else if (type == Float.class || "float".equals(type.getName()))
			{
				if (StringUtils.isBlank(v))
					return -1;
				return Float.valueOf(StringUtils.trim(v));
			}
			else if (type == Double.class || "double".equals(type.getName()))
			{
				if (StringUtils.isBlank(v))
					return -1;
				return Double.valueOf(StringUtils.trim(v));
			}
			else if (type == String.class)
			{
				return v;
			}
			else if (type == Calendar.class)
			{
				java.util.Date d = castStringToDate(v);
				if (null == v)
					return null;
				Calendar c = Calendar.getInstance();
				c.setTime(d);
				return c;
			}
			else if (type == java.util.Date.class)
			{
				return castStringToDate(v);
			}
			else if (type == Timestamp.class)
			{
				return new Timestamp(castStringToDate(v).getTime());
			}
			else if (type == org.ihelpuoo.localize.Date.class)
			{
				java.util.Date d = castStringToDate(v);
				if (null == v)
					return null;
				return org.ihelpuoo.localize.Date.createInstance(d.getTime());
			}
			else if (type == TimeZone.class)
			{
				return TimeZone.getTimeZone(v);
			}
			else if (type == StringBuffer.class)
			{
				return new StringBuffer(v);
			}
			else if (type == InputStream.class)
			{
				return new CharInputStream(v);
			}
			else if (type == CharSequence.class)
			{
				return v;
			}
		}
		catch (Exception e)
		{
			throw new CastValueException("Can not case \"" + v + "\" to " + type.getName());
		}
		throw new CastValueException("Can not case \"" + v + "\" to " + type.getName());
	}

	public static java.util.Date castStringToDate(String s)
	{
		SimpleDateFormat f = null;
		if (s.indexOf(' ') < 5)
			f = new SimpleDateFormat(DF_DATE);
		else
			f = new SimpleDateFormat(DF_DATETIME);
		try
		{
			return f.parse(s);
		}
		catch (Throwable e)
		{
			return null;
		}
	}

	public static String castDateToString(java.util.Date date, String pattern)
	{
		SimpleDateFormat f = new SimpleDateFormat(pattern);
		try
		{
			return f.format(date);
		}
		catch (Exception e)
		{
			return null;
		}
	}

	public static String castDateToDateString(java.util.Date date)
	{
		return castDateToString(date,DF_DATE);
	}

	public static String castDateToDateTimeString(java.util.Date date)
	{
		return castDateToString(date,DF_DATETIME);
	}

	// @ TODO ZZH: make it configable
	public static Object cast(Class type, Object v) throws CastValueException
	{
		if (null == v)
			return null;
		if (type == v.getClass())
			return v;
		if (isChildOf(v.getClass(),type))
			return v;
		try
		{
			if (v instanceof java.util.Date)
			{
				if (type == Calendar.class)
				{
					Calendar c = Calendar.getInstance();
					c.setTime((java.util.Date)v);
					return c;
				}
				else if (type == java.sql.Timestamp.class)
				{
					java.sql.Timestamp t = new java.sql.Timestamp(((java.util.Date)v).getTime());
					return t;
				}
				else if (Long.class == type)
				{
					return ((java.util.Date)v).getTime();
				}
			}
			else if (Integer.class == type)
			{
				if (v instanceof Boolean)
				{
					if (((Boolean)v).booleanValue())
						return new Integer(1);
					else
						return new Integer(0);
				}
				else
				{
					try
					{
						return Integer.valueOf(v.toString());
					}
					catch (NumberFormatException e)
					{
						return new Integer(-1);
					}
				}
			}
			else if (v instanceof Calendar)
			{
				if (java.util.Date.class == type)
				{
					return ((Calendar)v).getTime();
				}
				else if (type == java.sql.Timestamp.class)
				{
					java.sql.Timestamp t = new java.sql.Timestamp(((Calendar)v).getTimeInMillis());
					return t;
				}
				else if (Long.class == type)
				{
					return ((Calendar)v).getTimeInMillis();
				}
				else
				{
					String s = ObjectUtils.castDateToDateTimeString(((Calendar)v).getTime());
					if (String.class == type)
						return s;
					return cast(type,s);
				}

			}
		}
		catch (Throwable e)
		{
			throw new CastValueException(e);
		}
		return cast(type,v.toString());
	}

	public static CharSequence castObjectToCharSequence(Object obj)
	{
		if (null == obj)
		{
			return null;
		}
		else if (obj instanceof CharSequence)
		{
			return (CharSequence)obj;
		}
		else if (obj instanceof Calendar)
		{
			return DateTimeUtils.convertToString((Calendar)obj);
		}
		else if (obj instanceof java.util.Date)
		{
			return DateTimeUtils.convertToString((java.util.Date)obj);
		}
		else if (obj instanceof org.ihelpuoo.localize.Date)
		{
			return ((org.ihelpuoo.localize.Date)obj).toString();
		}
		else if (obj instanceof TimeZone)
		{
			return ((TimeZone)obj).getID();
		}
		else
		{
			return obj.toString();
		}
	}

	public static InputStream castObjectToInputStream(Object srcObj) throws CastValueException
	{
		if (null == srcObj)
			throw new CastValueException("srcObject can not be null!");
		if (srcObj instanceof CharSequence)
		{
			return new CharInputStream(((CharSequence)srcObj).toString());
		}
		else if (srcObj instanceof InputStream)
		{
			return (InputStream)srcObj;
		}
		else if (srcObj instanceof File)
		{
			try
			{
				return new FileInputStream((File)srcObj);
			}
			catch (FileNotFoundException e)
			{
				throw new CastValueException(e);
			}
		}

		Method[] methods = srcObj.getClass().getMethods();
		try
		{
			for (int i = 0; i < methods.length; i++)
			{
				Method m = methods[i];
				if (m.getParameterTypes().length > 0)
					continue;
				Class rt = m.getReturnType();

				if (isChildOf(rt,InputStream.class))
				{
					return (InputStream)m.invoke(srcObj,(Object[])null);
				}
			}
		}
		catch (Exception e)
		{
			throw new CastValueException(e);
		}
		throw new CastValueException("Can not scoop InputStream from srcObj");
	}

	public static boolean equals(Object a1, Object a2)
	{
		if (a1 == a2)
			return true;
		if (a1 == null || a2 == null)
			return false;
		return a1.equals(a2);
	}

	public static boolean isInteger(Class type)
	{
		if (type == Byte.class || "byte".equals(type.getName()))
			return true;
		else if (type == Short.class || "short".equals(type.getName()))
			return true;
		else if (type == Integer.class || "int".equals(type.getName()))
			return true;
		else if (type == Long.class || "long".equals(type.getName()))
			return true;
		return false;
	}

	public static boolean isBoolean(Class type)
	{
		if (type == Boolean.class || "boolean".equals(type.getName()))
			return true;
		return false;
	}

	public static Object duplicate(Object obj)
	{
		return duplicateAs(obj,obj.getClass());
	}

	public static Object duplicateAs(Object obj, Class type)
	{
		Object o = newInstance(type);
		Field[] fs = getAllMyStdFields(obj.getClass());
		for (int i = 0; i < fs.length; i++)
		{
			Object v = getFieldValue(obj,fs[i]);
			if (null == v)
				continue;
			try
			{
				Method setter = getSetter(type,fs[i]);
				setter.invoke(o,v);
			}
			catch (Exception e)
			{
			}
		}
		return o;
	}

	// @ TODO ZZH: think about multiple level inner class.
	public static Object newInstance(Class klass)
	{
		try
		{
			return klass.newInstance();
		}
		catch (Exception e)
		{
			Class oc = getOuterClass(klass);
			if (null == oc)
				return null;
			// For inner class
			try
			{
				Constructor c = klass.getDeclaredConstructor(oc);
				Object p = newInstance(oc);
				return c.newInstance(p);
			}
			catch (Exception e1)
			{
				return null;
			}
		}
	}

	public static Class getOuterClass(Class klass)
	{
		String name = klass.getName();
		int pos = name.lastIndexOf('.');
		if (pos > 0)
			name = name.substring(0,pos);
		pos = name.lastIndexOf('$');
		if (pos == -1)
			return null;
		StringBuffer sb = new StringBuffer(klass.getPackage() != null ? klass.getPackage()
				.getName() : "");
		if (sb.length() > 0)
			sb.append('.');
		sb.append(name.substring(0,pos));
		try
		{
			return Class.forName(sb.toString());
		}
		catch (ClassNotFoundException e)
		{
			return null;
		}
	}

	public static void writeToFile(Object obj, File f) throws IOException
	{
		Writer w = null;
		try
		{
			FileUtils.createNewFile(f);
			w = new FileWriter(f);
			w.write(obj.toString());
			w.close();
		}
		catch (IOException e)
		{
			StreamUtils.safeClose(w);
			throw e;
		}
	}
}
